원본 항목: Part 4. 에러 처리 — 1. 오류 메시지 통한 정보 노출CWE-209repo 폴더: sd40_errmsg/ (빌드·실행은 §3-1)
목표 시간: 1회차 10분 / 2회차 6분 / 3회차 4분
Part 4(에러 처리)의 첫 드릴이다.
mock 없이 문자열 비교만으로 완전히 재현된다 — “사용자에게 보이는 채널”과 “내부 로그 채널”을 분리하는 것 자체가 방어다.
이 드릴의 문서 구성 (SD01~SD58 공통)
① 요구사항서 — 어떤 코드가 뚫려 있고, 무엇을 만드나.
② pseudo code 템플릿 — Bad 와 Good 의 뼈대이고, 회차마다 가린다.
③ 테스트 코드 — 공격은 막히고 정상은 통과해야 끝이다.
④ 자주 하는 실수
⑤ 모범답안 — 맨 뒤에 접어 둔다.
변형 A/B/C 는 같은 치환 규칙을 다른 소재로 다시 치는 것이다.
0. 이 드릴로 체화할 것
- snprintf(user_msg, size, "Error: %s", detail); /* 상세 정보를 사용자에게 그대로 노출 */+ snprintf(user_msg, size, "요청 처리 중 오류가 발생했습니다."); /* 사용자에게는 고정된 일반 메시지 */+ snprintf(log_msg, size, "[internal] %s", detail); /* 상세 정보는 로그로만 */
상세 에러는 없애는 게 아니라 채널을 분리하는 것이다.
사용자에게는 단일하고 무의미한 메시지, 운영자에게는(로그를 통해) 그대로.
회차별 진행
회차
변형
보는 것
순서
1회차
A 설정 파일 로드 실패 메시지
① 요구사항서 ② pseudo code 전체
1. Bad 를 그대로 쳐서 취약점을 관찰한다. 2. Good pseudo 를 한 줄씩 코드로 바꾼다. 3. ③ 테스트를 실행한다.
2회차
B DB 연결 실패 메시지로 변형
① 요구사항서 ② pseudo code 의 번호와 제목만 (Level 2)
1. Bad 를 치고 취약점을 확인한다. 2. 번호만 보고 Good 을 쓴다. 3. ③ 테스트를 실행한다.
3회차
C 오류 코드 매핑 테이블 추가
① 요구사항서만
1. ③ 테스트를 먼저 읽는다. 2. 백지에서 Bad 부터 Good 까지 재현한다.
1회차에 막히면 같은 변형을 한 번 더 친다.
3회차를 통과하면 A 를 백지로 다시 친다.
void report_error_good(const char *detail, char *user_msg, size_t user_msg_size, char *log_msg, size_t log_msg_size){ /* [ ] 1. 매개변수에 log_msg, log_msg_size 를 **추가**한다 */ /* [ ] 2. 사용자 메시지는 고정 문자열 */ /* [ ] 3. 상세 정보는 로그로만 */}
2-C. report_error_good — Level 3 (3회차, 백지)
void report_error_good(const char *detail, char *user_msg, size_t user_msg_size,{ /* 아무것도 안 본다. * §1 요구사항서와 §0 의 치환 규칙만 보고 친다. * * 막히면 아래만 찾아본다. * - 상세 에러를 없애는 게 아니라 **채널을 분리**한다 * - `"%s"` 로 고정 문자열을 넘긴다 */}
막혔을 때 찾는 순서
컴파일 에러가 나면 에러 문구를 본다.
unknown type name / implicit declaration 이면 include 누락이다.
man 3 <함수> 를 연다.
SYNOPSIS 맨 위의 #include 를 확인한다.
로직이 막히면 §3 테스트의 기대값을 먼저 읽는다.
공격 입력표(§1)와 테스트가 답이다.
#include <stdio.h>#include "errmsg.h"void report_error_bad(const char *detail, char *user_msg, size_t user_msg_size){ snprintf(user_msg, user_msg_size, "Error: %s", detail); /* 상세 정보를 그대로 노출 */}void report_error_good(const char *detail, char *user_msg, size_t user_msg_size, char *log_msg, size_t log_msg_size){ snprintf(user_msg, user_msg_size, "%s", "요청 처리 중 오류가 발생했습니다."); snprintf(log_msg, log_msg_size, "[internal] %s", detail); /* 상세 정보는 로그로만 */}
눈여겨볼 점: report_error_good의 매개변수가 report_error_bad보다 2개 더 많다(log_msg, log_msg_size).
함수 시그니처 자체가 “출력 채널이 두 개로 분리되어 있다”고 선언하는 셈이다 — 안전한 설계는 종종 매개변수 개수의 증가로 드러난다.